﻿using System;
using System.Collections.Generic;
using System.Xml;
using System.Text;
using SdmxMl.Common;
using SdmxMl.Helper;
using System.Windows.Forms;

namespace SdmxMl.Structure
{
    /// <summary>
    /// GroupType declares any useful groupings of data, based on a selection of the declared (non-Time) dimensions
    /// (indicated with the DimensionRef element) which form partial keys to which attributes may be attached.
    /// The value of the DimensionRef element is the concept of the dimension - that is, the value of the
    /// dimension's concept attribute. Thus, if data is to be presented as a set of time series which vary
    /// only according to their differing frequencies, a "sibling group" would be declared, with all dimensions
    /// except the frequency dimension in it. If data is commonly grouped as a set of all countries, then a
    /// "Country Group" could be declared, with all dimensions except the country dimension forming part of
    /// the partial key. Any dimension which is not part of a group has a value which varies at the series level
    /// (for time series formats). There is no requirement to have only a single dimension ommitted from a
    /// partial key - it can be any subset of the set of ordered dimensions (that is, all dimensions except
    /// the time dimension, which may never be declared as belonging to a group partial key).
    /// All groups declared in the key family must be unique - that is, you may not have duplicate
    /// partial keys. All groups must also be given unique names (id attributes). Although it is conventional
    /// to declare dimensions in the same order as they are declared in the ordered key, there is no
    /// requirement to do so - the ordering of the values of the key are taken from the order in which
    /// the dimensions are declared. The Description element provides a human-readable description
    /// (potentially in multiple, parallel languages) of the group. Note that for cross-sectional
    /// formats, the named group mechanism is not used, but is instead replaced by a generic group
    /// which carries time and frequency values with it, and allows for any available group-level
    /// attributes to be specified if desired.
    /// </summary>
    public class GroupType : ISearchIdName, IAnnotationList
    {
        #region ISearchIdName Members

        public MultiLang Name
        {
            get { return Description; }
        }

        private string _id;
        public string Id
        {
            get { return _id; }
            set { _id = value; }
        }

        #endregion

        #region Properties
        
        private string _attachmentConstraintRef;
        public string AttachmentConstraintRef
        {
            get { return _attachmentConstraintRef; }
            set { _attachmentConstraintRef = value; }
        }

        private DimensionRefList _dimensionRefList;
        public DimensionRefList DimensionRefList
        {
            get { return _dimensionRefList; }
        }

        private MultiLang descr;
        public MultiLang Description
        {
            get { return descr; }
            set { descr = value; }
        }

        /// <summary></summary>
        private AnnotationList annotationList;
        public AnnotationList AnnotationList
        {
            get { return annotationList; }
            set { annotationList = value; }
        }

        #endregion //Properties

        #region Constructors

        public GroupType()
        {
            Init();
        }

        private void Init()
        {
            Description = new MultiLang();
            AnnotationList = new AnnotationList();
            _id = "Undefined";
            _dimensionRefList = new DimensionRefList();
        }


        public GroupType(XmlNode nodeArtefact, SdmxNamespaceManager nsmgr)
        {
            Init();
            _id = Xmlhelper.GetAtt(nodeArtefact, "id");
            MultiLangNodeReader mr = new MultiLangNodeReader();
            mr.Read(descr, nodeArtefact, "Description", nsmgr, false);

            XmlNodeList nodeList = null;
            if (nsmgr.VersionSdmx == SdmxVersion.SDMX_20)
            {
                nodeList = nodeArtefact.SelectNodes("./structure:DimensionRef", nsmgr);
                foreach (XmlNode nr in nodeList)
                    _dimensionRefList.Add(nr.InnerText);

                nodeList = nodeArtefact.SelectNodes("./structure:Annotations/common:Annotation", nsmgr);
            }
            else // SDMX 2.1
            {
                nodeList = nodeArtefact.SelectNodes("./structure:GroupDimension/structure:DimensionReference/Ref", nsmgr);
                foreach (XmlNode nr in nodeList)
                    _dimensionRefList.Add(Xmlhelper.GetAtt(nr, "id"));

                nodeList = nodeArtefact.SelectNodes("./common:Annotations/common:Annotation", nsmgr);
            }


            foreach (XmlNode na in nodeList)
            {
                Annotation a = new Annotation(na, nsmgr);
                annotationList.Add(a);
            }
        }

        #endregion //Constructors

        #region XML serialize

        public void WriteXml(XmlTextWriter xw, string prefix, string tag)
        {
            xw.WriteStartElement(prefix, tag, null);

             // id required
            xw.WriteAttributeString("id", _id);

            if (SdmxNamespaceManager.SdmxTarget == SdmxVersion.SDMX_20)
            {
                foreach (string s in _dimensionRefList)
                    Xmlhelper.WriteElementIfExist(xw, prefix, "DimensionRef", s);

                Description.WriteXml(xw, prefix, "Description");

                // Annotations
                AnnotationList.WriteXml(xw, prefix, null);
            }
            else // SDMX 2.1
            {
                // Annotations
                AnnotationList.WriteXml(xw, "common", null);

                foreach (string s in _dimensionRefList)
                {
                    xw.WriteStartElement(prefix, "GroupDimension", null);
                    xw.WriteStartElement(prefix, "DimensionReference", null);
                    xw.WriteStartElement("Ref", null);
                    Xmlhelper.WriteAttributeIfExist(xw, "id", s);
                    xw.WriteEndElement();
                    xw.WriteEndElement();
                    xw.WriteEndElement();
                }
            }

            xw.WriteEndElement();
        }
        #endregion //XML serialize

        #region String Representation

        public override string ToString()
        {
            return _id + " - " + descr.ToString();
        }
        #endregion //String Representation

        #region Nodes

        public TreeNode FillNode(TreeNode parentNode)
        {
            TreeNode t = new TreeNode(this.ToString());
            t.Tag = this;
            parentNode.Nodes.Add(t);
            return t;
        }

        #endregion //Nodes

    }

    public class DimensionRefList : List<string>
    {
    }

    public class GroupTypeList : List<GroupType>, ISdmxNewChild, ISdmxDelChild
    {
        private KeyFamilyType _parentDsd;
        public KeyFamilyType ParentDsd
        {
            get { return _parentDsd; }
        }

        public GroupTypeList(KeyFamilyType dsd)
            : base()
        {
            _parentDsd = dsd;
        }

        #region ISdmxNewChild Members

        public TreeNode NewChild(TreeNode node)
        {
            GroupType grp = new GroupType();
            this.Add(grp);
            TreeNode t = grp.FillNode(node);
            return t;
        }
        #endregion // ISdmxNewChild Members

        #region String Representation

        public override string ToString()
        {
            return "Groups";
        }
        #endregion

        #region ISdmxDelChild Members

        public void DelChild(object o)
        {
            GroupType grp = o as GroupType;
            if (grp != null)
             this.Remove(grp);
        }

        #endregion

        public GroupType GetGroupById(string id)
        {
            foreach (GroupType g in this)
                if (g.Id == id)
                    return g;
            return null;
        }
    }
}
